﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DiscreteMath
{
    /// <summary>
    /// Represents a single state in a finite state machine.
    /// </summary>
    [DebuggerDisplay("{Name}")]
    public struct State : IEquatable<State>, IComparable, IComparable<State>
	{
        private string name;

		[JsonConstructor]
        /// <summary>
        /// Initializes a new instance of <see cref="State"/> with the specified name.
        /// </summary>
        /// <param name="name">The name of the state.</param>
		public State(string name)
		{
            if (name == null)
                throw new ArgumentNullException("name");

			this.name = name;
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="State"/> struct based on the specified set of states.
        /// The name of the state = states.Print();
        /// </summary>
        /// <param name="states">A set of states to initialize the name from.</param>
        public State(ISet<State> states)
        {
            if (states == null)
                throw new ArgumentNullException("states");

            this.name = Print(states);
        }

        /// <summary>
        /// Gets the "undefined" state. A special state that represents a transition to a non-existing state. Should not
        /// be used for other purposes.
        /// </summary>
        public static State Undefined
        {
            get { return SpecialSigns.EmptyWord; }
        }

        [JsonProperty("name")]
        /// <summary>
        /// Gets the name of state, that is the unique identified the state represents.
        /// </summary>
        public string Name
        {
            get
            {
                if (this.name == null)
                    this.name = State.Undefined;
                return this.name;
            }
        }

        private static string Print(IEnumerable<State> enumerable)
		{
			StringBuilder builder = new StringBuilder();
			builder.Append("{ ");
			foreach (var state in enumerable)
			{
				builder.Append(state + ", ");
			}
			builder.Append("}");
			return builder.ToString().Replace(", }", " }");
		}

        #region IEquatable, IComparable, object overrides

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
        /// </returns>
        public bool Equals(State other)
        {
            if (object.ReferenceEquals(other, null))
                return false;

            return this.Name == other.Name;
        }

        public static bool operator ==(State first, State second)
        {
            return first.Equals(second);
        }

        public static bool operator !=(State first, State second)
        {
            return !first.Equals(second);
        }

        /// <summary>
        /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
        /// </summary>
        /// <param name="obj">An object to compare with this instance.</param>
        /// <returns>
        /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref name="obj" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref name="obj" />. Greater than zero This instance follows <paramref name="obj" /> in the sort order.
        /// </returns>
        /// <exception cref="System.ArgumentException">obj is not an State as this instance.;obj</exception>
        public int CompareTo(object obj)
        {
            if (!(obj is State))
                throw new ArgumentException("obj is not an State as this instance.", "obj");

            return string.CompareOrdinal(this.Name, ((State)obj).Name);
        }

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the <paramref name="other" /> parameter.Zero This object is equal to <paramref name="other" />. Greater than zero This object is greater than <paramref name="other" />.
        /// </returns>
        public int CompareTo(State other)
        {
            if (other == null)
                return 1;

            return string.CompareOrdinal(this.Name, other.Name);
        }

        public static bool operator <(State first, State second)
        {
            return first.CompareTo(second) == -1;
        }

        public static bool operator >(State first, State second)
        {
            return first.CompareTo(second) == 1;
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
		{
            if (!(obj is State))
                return false;

			return this.Equals((State)obj);
		}

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
		public override int GetHashCode()
		{
			return this.Name.GetHashCode();
        }

        #endregion

        #region Casting operators

        public static implicit operator State(string value)
		{
			return new State(value);
		}

		public static implicit operator string(State value)
		{
			return value.Name;
		}

		public static implicit operator State(char value)
		{
			return new State(value.ToString());
		}

        #endregion

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
		{
			return this.Name;
		}
    }
}
